using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using ZoneFiveSoftware.Common.Data;
using ZoneFiveSoftware.Common.Data.Fitness;
using ZoneFiveSoftware.Common.Data.Measurement;
using ZoneFiveSoftware.Common.Visuals;
using FilteredStatistics.Common;
using FilteredStatistics.Common.Controller;
using FilteredStatistics.Controller;
using FilteredStatistics.View;

namespace FilteredStatistics.Data
{
    class ElevationFilterCriteria : FilterCriteriaFacade
    {
        public ElevationFilterCriteria() :
            this(null, 100, 0, 2000, Length.Units.Meter)
        {
        }

        public ElevationFilterCriteria(IActivity activity,
                                       float gapSize,
                                       float maximumElevation,
                                       float startOffset,
                                       Length.Units elevationUnit)
        {
            m_ActivityCallbackHelper = new ActivityDataChangedHelper(activity);
            m_ActivityCallbackHelper.PropertyChanged += new PropertyChangedEventHandler(OnActivityDataChanged);

            Init(gapSize, maximumElevation, startOffset, elevationUnit);
        }

#region FilterCriteriaFacade members

        public override Guid ReferenceId
        {
            get { return GUIDs.ElevationCriteriaGuid; }
        }

        public override IActivity Activity
        {
            set
            {
                if (m_Activity != value)
                {
                    m_Activity = value;
                    m_ActivityCallbackHelper.Activity = m_Activity;

                    foreach (ElevationNamedZone namedZone in m_NamedZones)
                    {
                        namedZone.Activity = m_Activity;
                    }
                }
            }
        }

        public override String DisplayName
        {
            get { return CommonResources.Text.LabelElevation; }
        }

        public override bool IsTemplateOnly
        {
            get { return false; }
        }

        public override bool IsMainViewOnly
        {
            get { return false; }
        }

        public override List<object> NamedZones
        {
            get { return m_NamedZones; }
        }

        public override bool IsSerializable
        {
            get { return true; }
        }

        public override UInt16 DataVersion
        {
            get { return Constants.CurrentVersion.VersionNumber; }
        }

        public override object DeserializeCriteria(Stream stream, UInt16 version)
        {
            DataVersion dataVersion = new DataVersion(version);
            ElevationFilterCriteria result = new ElevationFilterCriteria();

            try
            {
                result.Deserialize(stream, dataVersion);
            }
            catch
            {
                return null;
            }

            return result;
        }

        public override void SerializeCriteria(Stream stream)
        {
            Serialize(stream);
        }

        public override object OnSelectedInList(object previousSelection)
        {
            if (ElevationCriteriaSelected != null)
            {
                // Make sure we have maximum 1 registered object since we have a return value
                Debug.Assert(ElevationCriteriaSelected.GetInvocationList().GetLength(0) == 1);

                IFilterCriteria result;
                ElevationCriteriaSelected(this, previousSelection as IFilterCriteria, out result);

                return result;
            }

            return previousSelection;
        }

        public override object TemplateCompatibleCriteria
        {
            get
            {
                return new ElevationFilterCriteria(m_Activity,
                                                   m_GapSize, m_MaximumElevation,
                                                   m_StartOffset, m_ElevationUnit);
            }
        }

#endregion

#region IPluginSerializable members

        public override void Serialize(Stream stream)
        {
            base.Serialize(stream);

            SerializableInt32Range elevationUnit = new SerializableInt32Range((Int32)m_ElevationUnit);
            SerializableDoubleRange maxElevation = new SerializableDoubleRange(m_MaximumElevation);
            SerializableDoubleRange elevationGap = new SerializableDoubleRange(m_GapSize);
            SerializableDoubleRange offset = new SerializableDoubleRange(m_StartOffset);

            elevationUnit.Serialize(stream);
            maxElevation.Serialize(stream);
            elevationGap.Serialize(stream);
            offset.Serialize(stream);
        }

        public void Deserialize_V0(Stream stream, DataVersion version)
        {
        }

        public void Deserialize_V5(Stream stream, DataVersion version)
        {
            SerializableInt32Range elevationUnit = new SerializableInt32Range((Int32)m_ElevationUnit);
            SerializableDoubleRange maxElevation = new SerializableDoubleRange(m_MaximumElevation);
            SerializableDoubleRange elevationGap = new SerializableDoubleRange(m_GapSize);
            SerializableDoubleRange offset = new SerializableDoubleRange(m_StartOffset);

            elevationUnit.Deserialize(stream, version);
            maxElevation.Deserialize(stream, version);
            elevationGap.Deserialize(stream, version);
            offset.Deserialize(stream, version);

            Init((float)elevationGap, (float)maxElevation,
                 (float)offset, (Length.Units)(Int32)elevationUnit);
        }

#endregion

        private void OnViewCurrentActivityChanged(IActivity currentActivity)
        {
            Activity = currentActivity;
        }

        private void OnActivityDataChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "Activity.Duration")
            {
                CreateSplitsNamedZonesList();
            }
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        public override bool Equals(object obj)
        {
            ElevationFilterCriteria criteria = obj as ElevationFilterCriteria;
            
            if (criteria != null)
            {
                return criteria.GapSize == GapSize &&
                       criteria.MaximumElevation == MaximumElevation &&
                       criteria.StartOffset == StartOffset &&
                       criteria.ElevationUnits == ElevationUnits;
            }

            return base.Equals(obj);
        }

        private void CreateSplitsNamedZonesList()
        {
            float startElevation = m_StartOffset;
            float endElevation = startElevation + m_GapSize;
            int zoneIndex = 0;

            // Create/update a named zone for each distance interval
            while (startElevation < m_MaximumElevation)
            {
                ElevationNamedZone currentNamedZone;

                if (m_NamedZones.Count > zoneIndex)
                {
                    // Reuse the already created named zone
                    currentNamedZone = m_NamedZones[zoneIndex] as ElevationNamedZone;
                    currentNamedZone.Init(m_Activity, startElevation, endElevation, m_ElevationUnit);
                }
                else
                {
                    // Create a new one
                    currentNamedZone = new ElevationNamedZone(m_Activity, startElevation, endElevation, m_ElevationUnit);
                    m_NamedZones.Add(currentNamedZone);
                }

                startElevation = endElevation;
                endElevation += m_GapSize;
                zoneIndex++;
            }

            // Remove excess named zones if required
            if (zoneIndex < m_NamedZones.Count)
            {
                m_NamedZones.RemoveRange(zoneIndex, m_NamedZones.Count - zoneIndex);
            }

            TriggerNamedZonesListChanged();
        }

        public void Init(float gapSize, float maximumElevation,
                         float offset, Length.Units elevationUnit)
        {
            m_GapSize = gapSize;
            m_StartOffset = offset;
            m_MaximumElevation = maximumElevation;
            m_ElevationUnit = elevationUnit;

            CreateSplitsNamedZonesList();
        }

        public float GapSize
        {
            get { return m_GapSize; }
        }

        public float StartOffset
        {
            get { return m_StartOffset; }
        }

        public float MaximumElevation
        {
            get { return m_MaximumElevation; }
        }

        public Length.Units ElevationUnits
        {
            get { return m_ElevationUnit; }
        }

        private IActivity m_Activity = null;
        private ActivityDataChangedHelper m_ActivityCallbackHelper = null;
        private float m_GapSize;
        private float m_StartOffset;
        private float m_MaximumElevation;
        private Length.Units m_ElevationUnit;
        private List<object> m_NamedZones = new List<object>();

        public delegate void ElevationCriteriaSelectedEventHandler(ElevationFilterCriteria criteria, IFilterCriteria previousCriteria, out IFilterCriteria resultCriteria);
        public event ElevationCriteriaSelectedEventHandler ElevationCriteriaSelected;
    }
}
